/*
 * Copyright 2012-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.context.config;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;

import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.BootstrapRegistry.Scope;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.DefaultPropertiesPropertySource;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributors.BinderOption;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.util.StringUtils;

/**
 * {@link ConfigurableEnvironment} 的包装器，用于导入并应用 {@link ConfigData}。
 * 通过包装来自 Spring {@link Environment} 的属性源并添加初始位置集合，来配置
 * {@link ConfigDataEnvironmentContributors} 的初始集合。
 * <p>
 * 初始位置可以通过 {@link #LOCATION_PROPERTY}、{@value #ADDITIONAL_LOCATION_PROPERTY}
 * 和 {@value #IMPORT_PROPERTY} 属性进行配置。如果没有显式设置这些属性，
 * 则会使用 {@link #DEFAULT_SEARCH_LOCATIONS} 中的默认搜索位置。
 *
 * @author Phillip Webb
 * @author Madhura Bhave
 */
class ConfigDataEnvironment {

	/**
	 * 用于覆盖导入位置的属性。
	 */
	static final String LOCATION_PROPERTY = "spring.config.location";

	/**
	 * 用于提供要额外导入的位置的属性。
	 */
	static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";

	/**
	 * 用于提供要额外导入的位置的属性。
	 */
	static final String IMPORT_PROPERTY = "spring.config.import";

	/**
	 * 用于在抛出 {@code ConfigDataNotFoundAction} 时决定采取什么操作的属性。
	 * @see ConfigDataNotFoundAction
	 */
	static final String ON_NOT_FOUND_PROPERTY = "spring.config.on-not-found";

	/**
	 * 如果未找到 {@link #LOCATION_PROPERTY}，则使用的默认搜索位置。
	 */
	static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
	static {
		List<ConfigDataLocation> locations = new ArrayList<>();
		locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
		locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
		DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
	}

	private static final ConfigDataLocation[] EMPTY_LOCATIONS = new ConfigDataLocation[0];

	private static final Bindable<ConfigDataLocation[]> CONFIG_DATA_LOCATION_ARRAY = Bindable
			.of(ConfigDataLocation[].class);

	private static final Bindable<List<String>> STRING_LIST = Bindable.listOf(String.class);

	private static final BinderOption[] ALLOW_INACTIVE_BINDING = {};

	private static final BinderOption[] DENY_INACTIVE_BINDING = { BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE };

	private final DeferredLogFactory logFactory;

	private final Log logger;

	private final ConfigDataNotFoundAction notFoundAction;

	private final ConfigurableBootstrapContext bootstrapContext;

	private final ConfigurableEnvironment environment;

	private final ConfigDataLocationResolvers resolvers;

	private final Collection<String> additionalProfiles;

	private final ConfigDataEnvironmentUpdateListener environmentUpdateListener;

	private final ConfigDataLoaders loaders;

	private final ConfigDataEnvironmentContributors contributors;

	/**
	 * 创建一个新的 {@link ConfigDataEnvironment} 实例。
	 *
	 * @param logFactory                延迟日志工厂，用于创建日志对象
	 * @param bootstrapContext          引导上下文（bootstrap context）
	 * @param environment               Spring 的 {@link Environment} 实例
	 * @param resourceLoader            用于加载资源路径的 {@link ResourceLoader}
	 * @param additionalProfiles        需要激活的额外 profile 集合
	 * @param environmentUpdateListener 可选的 {@link ConfigDataEnvironmentUpdateListener}，
	 *                                  可用于跟踪 {@link Environment} 的更新
	 */
	ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
			ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles,
			ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
		Binder binder = Binder.get(environment);
		UseLegacyConfigProcessingException.throwIfRequested(binder);
		this.logFactory = logFactory;
		this.logger = logFactory.getLog(getClass());
		// 绑定 spring.config.on-not-found 对应的属性
		this.notFoundAction = binder.bind(ON_NOT_FOUND_PROPERTY, ConfigDataNotFoundAction.class)
				.orElse(ConfigDataNotFoundAction.FAIL);
		this.bootstrapContext = bootstrapContext;
		this.environment = environment;
		this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
		this.additionalProfiles = additionalProfiles;
		this.environmentUpdateListener = (environmentUpdateListener != null) ? environmentUpdateListener
				: ConfigDataEnvironmentUpdateListener.NONE;
		this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, resourceLoader.getClassLoader());
		this.contributors = createContributors(binder);
	}

	protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
			ConfigurableBootstrapContext bootstrapContext, Binder binder, ResourceLoader resourceLoader) {
		return new ConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
	}

	private ConfigDataEnvironmentContributors createContributors(Binder binder) {
		this.logger.trace("Building config data environment contributors");
		MutablePropertySources propertySources = this.environment.getPropertySources();
		List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(propertySources.size() + 10);
		PropertySource<?> defaultPropertySource = null;
		for (PropertySource<?> propertySource : propertySources) {
			if (DefaultPropertiesPropertySource.hasMatchingName(propertySource)) {
				// ⭐️ 如果属性源的名称是 defaultProperties，则设置为默认属性源
				defaultPropertySource = propertySource;
			}
			else {
				this.logger.trace(LogMessage.format("Creating wrapped config data contributor for '%s'",
						propertySource.getName()));
				contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
			}
		}
		// 🚚 获取初始化导入贡献者，并添加到贡献者列表中
		contributors.addAll(getInitialImportContributors(binder));
		if (defaultPropertySource != null) {
			this.logger.trace("Creating wrapped config data contributor for default property source");
			contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
		}
		// 🏗️ 创建并返回最终的贡献者集合
		return createContributors(contributors);
	}

	protected ConfigDataEnvironmentContributors createContributors(
			List<ConfigDataEnvironmentContributor> contributors) {
		return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors);
	}

	ConfigDataEnvironmentContributors getContributors() {
		return this.contributors;
	}

	private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
		List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
		//📥 通过 spring.config.import 属性引入的导入贡献者
		addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
		//➕ 通过 spring.config.additional-location 属性引入的导入贡献者
		addInitialImportContributors(initialContributors,
				bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
		//📍 通过 spring.config.location 属性引入的导入贡献者
		addInitialImportContributors(initialContributors,
				bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
		return initialContributors;
	}

	private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other) {
		return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other);
	}

	private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors,
			ConfigDataLocation[] locations) {
		for (int i = locations.length - 1; i >= 0; i--) {
			initialContributors.add(createInitialImportContributor(locations[i]));
		}
	}

	private ConfigDataEnvironmentContributor createInitialImportContributor(ConfigDataLocation location) {
		this.logger.trace(LogMessage.format("Adding initial config data import from location '%s'", location));
		return ConfigDataEnvironmentContributor.ofInitialImport(location);
	}

	/**
	 * 处理所有配置项，并将任何新导入的属性源应用到 {@link Environment}。
	 */
	void processAndApply() {
		// ✨ 创建配置数据导入器
		ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,
				this.loaders);
		// 🔧 注册初始 Binder，保证早期配置能被读取
		registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
		// 📦 处理初始的导入位置
		ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
		// 🎯 构建激活上下文，创建 context 用于后续阶段判断哪些 profile 被激活，以及哪些资源是“活动”的。
		ConfigDataActivationContext activationContext = createActivationContext(
				contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
		// 🌐 处理 “无 Profile 导入”，在没有任何 profile 时，处理适用于所有环境的资源，加载不受 profile 影响的通用配置。
		contributors = processWithoutProfiles(contributors, importer, activationContext);
		// 🔥 激活 Profiles，解析已经加载到的配置（如 spring.profiles.active），并更新 activationContext 中的 profile 集合。
		activationContext = withProfiles(contributors, activationContext);
		// 🗂️ 处理有 Profile 的导入，根据最终确定的 profile 集合，加载所有带 profile 后缀的配置资源
		// （如 application-dev.yml、configserver-prod.yml 等）。
		contributors = processWithProfiles(contributors, importer, activationContext);
		// 💾 应用到 Environment，把找到的配置资源（包括强制加载与可选加载）注入 Environment 中，
		// 确保它们能够被 @ConfigurationProperties、@Value 等读取。
		applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(),
				importer.getOptionalLocations());
	}

	private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors,
			ConfigDataImporter importer) {
		this.logger.trace("Processing initial config data environment contributors without activation context");
		// 🔁 加载并处理最初的一批配置导入（此时还不知道 profile）
		contributors = contributors.withProcessedImports(importer, null);
		// 🔧 再一次注册 binder，使这些初步加载的配置可用于早期属性绑定
		registerBootstrapBinder(contributors, null, DENY_INACTIVE_BINDING);
		return contributors;
	}

	private ConfigDataActivationContext createActivationContext(Binder initialBinder) {
		this.logger.trace("Creating config data activation context from initial contributions");
		try {
			return new ConfigDataActivationContext(this.environment, initialBinder);
		}
		catch (BindException ex) {
			if (ex.getCause() instanceof InactiveConfigDataAccessException) {
				throw (InactiveConfigDataAccessException) ex.getCause();
			}
			throw ex;
		}
	}

	private ConfigDataEnvironmentContributors processWithoutProfiles(ConfigDataEnvironmentContributors contributors,
			ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
		this.logger.trace("Processing config data environment contributors with initial activation context");
		contributors = contributors.withProcessedImports(importer, activationContext);
		registerBootstrapBinder(contributors, activationContext, DENY_INACTIVE_BINDING);
		return contributors;
	}

	private ConfigDataActivationContext withProfiles(ConfigDataEnvironmentContributors contributors,
			ConfigDataActivationContext activationContext) {
		this.logger.trace("Deducing profiles from current config data environment contributors");

		// 🧵 构建 Binder，用于从 配置数据 中绑定 profile 相关配置项
		// ❗ 过滤掉带有 IGNORE_PROFILES 选项的 贡献者
		// 🚫 绑定到未激活的数据源时失败
		Binder binder = contributors.getBinder(activationContext,
				(contributor) -> !contributor.hasConfigDataOption(ConfigData.Option.IGNORE_PROFILES),
				BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
		try {
			// ➕ 拷贝已有的 附加的Profiles
			Set<String> additionalProfiles = new LinkedHashSet<>(this.additionalProfiles);

			// 🔗 合并 include 的 profiles（如 spring.profiles.include 配置）
			additionalProfiles.addAll(getIncludedProfiles(contributors, activationContext));

			// 🧠 创建 Profiles 对象，包含 environment、binder 和完整的 profile 集合
			Profiles profiles = new Profiles(this.environment, binder, additionalProfiles);

			// 🔄 返回一个更新后的 activationContext，包含新推导出的 Profiles 对象
			return activationContext.withProfiles(profiles);
		}
		catch (BindException ex) {
			// ❗ 如果是访问未激活配置导致的绑定异常，则直接抛出该特定异常
			if (ex.getCause() instanceof InactiveConfigDataAccessException) {
				throw (InactiveConfigDataAccessException) ex.getCause();
			}
			// ⚠️ 否则抛出原始绑定异常
			throw ex;
		}
	}

		private Collection<? extends String> getIncludedProfiles(ConfigDataEnvironmentContributors contributors,
				ConfigDataActivationContext activationContext) {
			// 🧩 创建用于解析占位符的 解析器（如 ${...}）
			PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
					contributors, activationContext, null, true);

			Set<String> result = new LinkedHashSet<>();

			// 🔁 遍历所有配置贡献者
			for (ConfigDataEnvironmentContributor contributor : contributors) {
				ConfigurationPropertySource source = contributor.getConfigurationPropertySource();

				// 🔍 如果该 配置绑定源 不为空，且未被标记为 IGNORE_PROFILES，则继续处理
				if (source != null && !contributor.hasConfigDataOption(ConfigData.Option.IGNORE_PROFILES)) {
					// 🧵 创建 Binder，用于从当前 配置绑定源 中提取配置项
					Binder binder = new Binder(Collections.singleton(source), placeholdersResolver);

					// 📌 尝试绑定 spring.profiles.include 为 List<String>
					binder.bind(Profiles.INCLUDE_PROFILES, STRING_LIST).ifBound((includes) -> {
						// ❗ 如果该 贡献者 尚未激活（profile 不匹配），检查是否非法访问
						if (!contributor.isActive(activationContext)) {
							// 🚫 如果尝试访问未激活配置中的 include 字段，则抛异常
							InactiveConfigDataAccessException.throwIfPropertyFound(contributor, Profiles.INCLUDE_PROFILES);
							InactiveConfigDataAccessException.throwIfPropertyFound(contributor,
									Profiles.INCLUDE_PROFILES.append("[0]"));
						}

						// ✅ 收集 include 的 profiles 到结果集中
						result.addAll(includes);
					});
				}
			}

			// 📤 返回最终收集的 spring.profiles.include profile 名集合
			return result;
		}

	private ConfigDataEnvironmentContributors processWithProfiles(ConfigDataEnvironmentContributors contributors,
			ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
		this.logger.trace("Processing config data environment contributors with profile activation context");

		// 🔁 重新处理配置导入（如 import: 配置），根据当前激活的 Profile 进行判断
		contributors = contributors.withProcessedImports(importer, activationContext);

		// 🧵 注册一个用于绑定配置属性的 Binder（绑定器）
		// ⚙️ 启用 ALLOW_INACTIVE_BINDING，允许绑定未激活的配置项（避免绑定报错）
		registerBootstrapBinder(contributors, activationContext, ALLOW_INACTIVE_BINDING);

		// 📤 返回处理后的 contributors，供后续配置加载使用
		return contributors;
	}

	private void registerBootstrapBinder(ConfigDataEnvironmentContributors contributors,
			ConfigDataActivationContext activationContext, BinderOption... binderOptions) {
		// 将当前贡献者们（contributors）转化为 Binder 能够使用的形式，这样 application 启动早期就能读取到初始的配置。
		this.bootstrapContext.register(Binder.class, InstanceSupplier
				.from(() -> contributors.getBinder(activationContext, binderOptions)).withScope(Scope.PROTOTYPE));
	}

	private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
			ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations,
			Set<ConfigDataLocation> optionalLocations) {
		// 🛡️ 校验配置中是否存在不合法的属性（例如占位符未解析等）
		checkForInvalidProperties(contributors);

		// ✅ 校验是否所有必须的配置位置都已加载
		checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);

		// 🎯 获取 Spring Environment 中的可变属性源容器
		MutablePropertySources propertySources = this.environment.getPropertySources();

		// 🧩 将所有配置贡献者应用到 Environment 的属性源中
		applyContributor(contributors, activationContext, propertySources);

		// 🔚 将默认属性（DefaultProperties）移动到属性源列表的末尾（优先级最低）
		DefaultPropertiesPropertySource.moveToEnd(propertySources);

		// 📦 获取最终解析后的 Profiles（默认和激活）
		Profiles profiles = activationContext.getProfiles();

		// 📢 设置 default profiles 到 Environment
		this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));
		this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));

		// 📢 设置 active profiles 到 Environment
		this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
		this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));

		// 🔁 通知 listener，profiles 设置完毕（可用于后续扩展）
		this.environmentUpdateListener.onSetProfiles(profiles);
	}

	private void applyContributor(ConfigDataEnvironmentContributors contributors,
			ConfigDataActivationContext activationContext, MutablePropertySources propertySources) {
		this.logger.trace("Applying config data environment contributions");

		// 🔁 遍历所有配置数据贡献者
		for (ConfigDataEnvironmentContributor contributor : contributors) {

			// 🎯 获取该贡献者对应的 PropertySource（即实际的配置属性源）
			PropertySource<?> propertySource = contributor.getPropertySource();

			// 📦 只处理类型为 BOUND_IMPORT 的贡献者，且其拥有配置属性源
			if (contributor.getKind() == ConfigDataEnvironmentContributor.Kind.BOUND_IMPORT && propertySource != null) {

				// ❌ 如果该贡献者未被激活（其所属 profile 不满足），则跳过
				if (!contributor.isActive(activationContext)) {
					this.logger.trace(
							LogMessage.format("Skipping inactive property source '%s'", propertySource.getName()));
				}
				// ✅ 否则，将该配置源加入到 Spring 的 Environment 属性源列表中
				else {
					this.logger
							.trace(LogMessage.format("Adding imported property source '%s'", propertySource.getName()));
					// 加到末尾，优先级较低
					propertySources.addLast(propertySource);

					// 📣 通知监听器，有新的配置源被添加（用于扩展）
					this.environmentUpdateListener.onPropertySourceAdded(propertySource, contributor.getLocation(),
							contributor.getResource());
				}
			}
		}
	}

	private void checkForInvalidProperties(ConfigDataEnvironmentContributors contributors) {
		for (ConfigDataEnvironmentContributor contributor : contributors) {
			InvalidConfigDataPropertyException.throwOrWarn(this.logger, contributor);
		}
	}

	private void checkMandatoryLocations(ConfigDataEnvironmentContributors contributors,
			ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations,
			Set<ConfigDataLocation> optionalLocations) {
		Set<ConfigDataLocation> mandatoryLocations = new LinkedHashSet<>();
		for (ConfigDataEnvironmentContributor contributor : contributors) {
			if (contributor.isActive(activationContext)) {
				mandatoryLocations.addAll(getMandatoryImports(contributor));
			}
		}
		for (ConfigDataEnvironmentContributor contributor : contributors) {
			if (contributor.getLocation() != null) {
				mandatoryLocations.remove(contributor.getLocation());
			}
		}
		mandatoryLocations.removeAll(loadedLocations);
		mandatoryLocations.removeAll(optionalLocations);
		if (!mandatoryLocations.isEmpty()) {
			for (ConfigDataLocation mandatoryLocation : mandatoryLocations) {
				this.notFoundAction.handle(this.logger, new ConfigDataLocationNotFoundException(mandatoryLocation));
			}
		}
	}

	private Set<ConfigDataLocation> getMandatoryImports(ConfigDataEnvironmentContributor contributor) {
		List<ConfigDataLocation> imports = contributor.getImports();
		Set<ConfigDataLocation> mandatoryLocations = new LinkedHashSet<>(imports.size());
		for (ConfigDataLocation location : imports) {
			if (!location.isOptional()) {
				mandatoryLocations.add(location);
			}
		}
		return mandatoryLocations;
	}

}
